/*
 *
 * Built in functions for older browsers 
 *
 */

if(!Array.prototype.indexOf) {
  
  Array.prototype.indexOf = function(elt /*, from*/) {
    var len = this.length;

    var from = Number(arguments[1]) || 0;
    from = (from < 0)
		? Math.ceil(from)
        : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++) {
      if (from in this && this[from] === elt)
		return from;
    }
    return -1;
  };
}

if(!Array.prototype.remove) {

	Array.prototype.remove = function(e) {
	  for (var i = 0; i < this.length; i++) {
		if (e == this[i]) { return this.splice(i, 1); }
	  }
	};
}

if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}

if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If IsCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if ({}.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 6. Let A be a new array created as if by the expression new Array(len) where Array is
    // the standard built-in constructor with that name and len is the value of len.
    A = new Array(len);

    // 7. Let k be 0
    k = 0;

    // 8. Repeat, while k < len
    while(k < len) {

      var kValue, mappedValue;

      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
        kValue = O[ k ];

        // ii. Let mappedValue be the result of calling the Call internal method of callback
        // with T as the this value and argument list containing kValue, k, and O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Call the DefineOwnProperty internal method of A with arguments
        // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
        // and false.

        // In browsers that support Object.defineProperty, use the following:
        // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });

        // For best browser support, use the following:
        A[ k ] = mappedValue;
      }
      // d. Increase k by 1.
      k++;
    }

    // 9. return A
    return A;
  };      
}

if (!Array.prototype.findFirst) {
	Array.prototype.findFirst = function(fun) {
		var len = this.length;
		if (typeof fun != "function")
			throw new TypeError();
		
		var res = false,
		thisp = arguments[1];
	  
		for (var i = 0; i < len; i++) {
			if (i in this) {
				var val = this[i]; // in case fun mutates this
				if (fun.call(thisp, val, i, this)) {
					res = val;
					break;
				}
			}
		}
		return res;
	};
}

if (!Array.prototype.forEach) {
	Array.prototype.forEach = function(fun /*, thisp*/) {
		var len = this.length;
		if (typeof fun != "function")
			throw new TypeError();

		var thisp = arguments[1];
		for (var i = 0; i < len; i++) {
			if (i in this)
				fun.call(thisp, this[i], i, this);
		}
	};
}

if (typeof Object.create !== 'function') {  
    Object.create = function (o) {  
        function F() {}  
        F.prototype = o;  
        return new F();  
    };  
}

if(!Object.keys) Object.keys = function(o){
	if (o !== Object(o))
      throw new TypeError('Object.keys called on non-object');
 	var ret=[],p;
 	for(p in o) if(Object.prototype.hasOwnProperty.call(o,p)) ret.push(p);
 	return ret;
}

if (!String.prototype.format) {

	String.prototype.format = function() {
	    var formatted = this;
	    for (var i = 0; i < arguments.length; i++) {
	        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
	        formatted = formatted.replace(regexp, arguments[i]);
	    }
	    return formatted;
	};
}



/*
 * util - JavaScript standard toolkit
 *
 * Copyright (c) 2011 
 * Michal Skowronek (http://www.coderitual.com)
 * Licence under LGPL
 *
 * Contact: skowronkow@gmail.com
 */
 
 
(function(global, undefined) {
	
	var util = {
	
		VERSION: '0.3.3'
	};
	
	// Extend method definition
	util.extend = function(obj, extObj) {
    
		if (arguments.length > 2) {
			for (var a = 1; a < arguments.length; a++) {
				util.extend(obj, arguments[a]);
			}	
		} else {
			for (var i in extObj) {
				if (extObj.hasOwnProperty(i))
					obj[i] = extObj[i];
			}
		}
		
		return obj;
	};
	
	// Creates delegate method from function(func), called with specified scope(assigned to 'this')
	util.bind = function(func, scope) {
		var _function = func;
  
		return function() {
			return _function.apply(scope, arguments);
		}
	};
	
	util.addEvent = function(elem, type, eventHandler) {
		
		if (elem == null || elem == undefined) 
			return;
		
		if ( elem.addEventListener ) {
			elem.addEventListener( type, eventHandler, false );
		} else if ( elem.attachEvent ) {
			elem.attachEvent( 'on' + type, eventHandler );
		} else {
            element[ 'on' + type ] = handler;
        }
	};

	util.stopPropagation = function(e)
	{
		if (!e) var e = window.event;
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
	};

	/*
	 * MicroEvent - to make any js object an event emitter (server or browser)
	 * 
	 * - pure javascript - server compatible, browser compatible
	 * - dont rely on the browser doms
	 * - super simple - you get it immediatly, no mistery, no magic involved
	 *
	 * - create a MicroEventDebug with goodies to debug
	 *   - make it safer to use
	*/

	var MicroEvent	= function(){}
	MicroEvent.prototype = {
		bind: function(event, fct){
			this._events = this._events || {};
			this._events[event] = this._events[event]	|| [];
			this._events[event].push(fct);
		},
		unbind: function(event, fct){
			this._events = this._events || {};
			if( event in this._events === false  )	return;
			this._events[event].splice(this._events[event].indexOf(fct), 1);
		},
		trigger: function(event /* , args... */){
			this._events = this._events || {};
			if( event in this._events === false  )	return;
			for(var i = 0; i < this._events[event].length; i++){
				this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
			}
		}
	};
	
	MicroEvent.mixin = function(destObject){
		var props	= ['bind', 'unbind', 'trigger'];
		for(var i = 0; i < props.length; i ++){
			destObject.prototype[props[i]]	= MicroEvent.prototype[props[i]];
		}
	};
	
	util.events = MicroEvent;

	util.is = function (o, type) {
    	type = String(type).toLowerCase();
	    return  (type == "null" && o === null) ||
	            (type == typeof o) ||
	            (type == "object" && o === Object(o)) ||
	            (type == "array" && Array.isArray && Array.isArray(o)) ||
	            Object.prototype.toString.call(o).slice(8, -1).toLowerCase() == type;
	};

	util.isArray = function(arrayObj) {
		return arrayObj && arrayObj.constructor == Array;
	}
	
	util.ready = function( f ) {
        if(document.addEventListener) {
            document.addEventListener( "DOMContentLoaded", f, false );
        } else {
            window.setTimeout( f, 0 );
        }
    };
	
	util.hasClass = function hasClass(ele,cls) {
		return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
	}
 
	util.addClass = function(ele,cls) {
		if (!util.hasClass(ele,cls)) ele.className += " "+cls;
	};
 
	util.removeClass = function (ele,cls) {
		if (util.hasClass(ele,cls)) {
	    	var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
			ele.className = ele.className.replace(reg,' ');
		}
	};

	// util.tmpl 
	// Simple JavaScript Templating
	// John Resig - http://ejohn.org/ - MIT Licensed
	(function(){
		var cache = {};

		util.tmpl = function tmpl(str, data){
		// Figure out if we're getting a template, or if we need to
		// load the template - and be sure to cache the result.
		var fn = !/\W/.test(str) ?
		  cache[str] = cache[str] ||
		    tmpl(document.getElementById(str).innerHTML) :
		  
		  // Generate a reusable function that will serve as a template
		  // generator (and which will be cached).
		  new Function("obj",
		    "var p=[],print=function(){p.push.apply(p,arguments);};" +
		    
		    // Introduce the data as local variables using with(){}
		    "with(obj){p.push('" +
		    
		    // Convert the template into pure JavaScript
		    str
		      .replace(/[\r\t\n]/g, " ")
		      .split("<%").join("\t")
		      .replace(/((^|%>)[^\t]*)'/g, "$1\r")
		      .replace(/\t=(.*?)%>/g, "',$1,'")
		      .split("\t").join("');")
		      .split("%>").join("p.push('")
		      .split("\r").join("\\'")
		  + "');}return p.join('');");

		// Provide some basic currying to the user
		return data ? fn( data ) : fn;
		};
	})();

	util.eachRecursive = function eachRecursive(obj, fun) {
	    for (var k in obj) {
	    	if(!obj[has](k)) continue;

	        if (util.is(obj[k], 'object')) {
	        	eachRecursive(obj[k], fun);
	        } else {
	        	fun.call(obj, k, obj[k]);
	        }  
	    }
	};

	// Expose namespace
	global.util = util;

})(window);

/*
 * JTop - JavaScript online desktop library
 *
 * uses: util toolkit
 *       Raphael
 *
 * Copyright (c) 2011 coderitual
 * Michal Skowronek (http://www.coderitual.com)
 * coderitual licence
 *
 * Contact: skowronkow@gmail.com
 */
 
(function(global, document, undefined) {

	var modulesDef = {},
		extensions = {},
	
	jt = {
		
		Version: '0.5.5',

		/*
		 * Summary: Register desktop module
		 * Arguments: Module name and constructor of module
		 */
		module: function(name, ctor) {
				
			if(ctor && !modulesDef[name]) {
				
				modulesDef[name] = ctor;
			
				return this;
			} else {	
				return false;
			}	
		},

		extension: function(extensionName, extensionObject) {
			if(extensions[extensionName])
				return;
			
			if(!extensionObject) 
				return;

			if(typeof(extensionObject) === 'object') {
				extensions[extensionName] = extensionObject;	
			} else if (typeof(extensionObject) === 'function') {
				extensions[extensionName] = new extensionObject(); 
			}

			return this;
		},
		
		
		desktop: function(options) {
			return new Desk(options);
		},
		
		toString: function() {
			return 'JTop library version: ' + this.Version;
		},
		
		DEBUG: false
	};
	
	/*
	 *	Helper functions
	 */
	 
	function disableSelection(target){

		if (typeof target.onselectstart!="undefined") //IE route
			target.onselectstart=function(){return false}

		else if (typeof target.style.MozUserSelect!="undefined") //Firefox route
			target.style.MozUserSelect="none"

		else //All other route (ie: Opera)
			target.onmousedown=function(){return false}

		target.style.cursor = "default"
	}

	function getViewportOffset(el) {
		
		var top = el.offsetTop;
		var left = el.offsetLeft;
		var width = el.offsetWidth;
		var height = el.offsetHeight;

		while(el.offsetParent) {
			el = el.offsetParent;
			top += el.offsetTop;
			left += el.offsetLeft;
		}

		return {
			top: top - window.pageYOffset,
			left: left - window.pageXOffset,
			bottom: (window.pageYOffset + window.innerHeight) - (top + height),
			right: (window.pageXOffset + window.innerWidth) - (left + width) 
		};
	}

	/*
	 * Summary: desktop class definition
	 * Arguments: Desktop options
	 */
	var Desk = function(options) {

		// Desktop settings. It is also exposed for modules.
		var settings = this._settings = util.extend({
			containerId: 'jtop',
			width: '100%',
			height: '100%',	
			grid: {
				h: 100,
				v: 100
			},
			version: jt.Version,
			edit: true
		}, options);

		// Shorthand for desktop
		var desk = this;
			
		// Container for instantiated modules
		this._modules = {};

		// Desktop items container
		this._items = {};

		// Id item counter used when item's id is generated
		this._idCounter = -1;

		// Signal handlers and extensions provided by modules
		this._events = {};

		this._container = document.getElementById(settings.containerId);
		this._canvas = Raphael(this._container, settings.width, settings.height);
		this._viewport = {
			offset: getViewportOffset(this._container)
		}

		// Disable text selection inside container
		disableSelection(this._container);
	
		// Desktop context. It is used by modules and it is only way to comunicate with each other.
		var context =  {
			settings: settings,
			canvas: desk._canvas,
			container: this._container,
			getItemById: util.bind(desk.getItemById, desk),
			getNextId: util.bind(desk._getId, desk),
			event: util.bind(desk.emitEvent, desk),
			on: util.bind(desk.addEventListener, desk),
			log: util.bind(this.log, desk),
			addItem: util.bind(desk.addItem, desk),
			removeItem: util.bind(desk.removeItem, desk),
			document: document,
			fn: fn,
			viewport: desk._viewport,

			require: function(extensionName) {
				if(extensions[extensionName]) {
					return extensions[extensionName];
				} else {
					throw '[context.require] extension doesn\'t exists: ' + extensionName;
				}
			}
		}

		// Default desktop events
		var containerOffsetLeft = this._container.offsetLeft,
		containerOffsetTop = this._container.offsetTop,
	
		doc = this._canvas.raphael,
		canvas = this._canvas;
	
		util.addEvent(window, "resize", function() { 
			containerOffsetLeft = desk._container.offsetLeft;
			containerOffsetTop = desk._container.offsetTop;
			desk._viewport.offset = getViewportOffset(desk._container);
			desk.emitEvent('desktopResize');
		});

		doc.mousemove(function(e, x, y) {
			context.globalEvent = window.event || e;
			desk.emitEvent('mouseMove', e, e.pageX - containerOffsetLeft, e.pageY - containerOffsetTop);
		});
		
		doc.mousedown(function(e) {
			desk.emitEvent('mouseDown', e);
		});
		
		doc.mouseup(function(e) {
			desk.emitEvent('mouseUp', e);
		});

		util.addEvent( document.documentElement, 'click', function( e ) {
       		desk.emitEvent('mouseClick', e);
		});

		util.addEvent( document.documentElement, 'dblclick', function( e ) {
       		desk.emitEvent('mouseDblClick', e);
		});

		// Modules creation and initialization
		for(var name in modulesDef) {
			
			try {
				var module = new modulesDef[name](context);
				this._modules[name] = module;

				// Register module events handlers
				if(module.events) {
							
					for (var eventName in module.events) {
						if (module.events.hasOwnProperty(eventName)) {
							this.addEventListener(eventName, util.bind(module.events[eventName], module));
						}
					}
				}
			} catch(e) {
				console.error('[ERROR] Exception during module [' + name + '] initialization: ' + e.message + '\nstack:' + e.stack);
			}
		}

		// Initialize modules
		this.emitEvent('init');

	};

	var fn = Desk.prototype = {
		
		/*
		 * Summary: Generates item id
		 */
		_getId: function() {
			return ++this._idCounter;
		},
		
		/*
		 * Summary: Gets item by id
		 * Arguments: Item id
		 */	
		getItemById: function(id) {
			return this._items[id];
		},

		extend: function() {
			if(util.is(arguments[0], 'object')) {
				util.extend(fn, arguments[0])
			} else if(util.is(arguments[0], 'string')) {
				if(!arguments[1])
					return;
				
				var obj = {};
				obj[arguments[0]] = arguments[1];

				util.extend(fn, obj);
			}
		},
		
		/*
		 * Summary: Send an event to listening modules
		 * Arguments: Event object with the attributes 'type' and params 'data'
		 */
		emitEvent: function(type) {
			if (this._events.hasOwnProperty(type)) {
				for (var i = 0; i < this._events[type].length; i++) {
					this._events[type][i].apply(this, Array.prototype.slice.call(arguments, 1));
				}
			}
		},
		
		/*
		 * Summary: Register an event listener
		 * Arguments: Event type and handler function
		 */
		addEventListener: function(type, handle) {
			
			if(!util.is(type, 'string') || !util.is(handle, 'function')) {
				return;
			}

			var types = type.split(' ');

			for(var i = 0, len = types.length; i < len; i++) {
				
				var t = types[i];
				if (this._events.hasOwnProperty(t)) {
					this._events[t].push(handle);
				} else {
					this._events[t] = [handle];
				}
			}
		},

		/*
		 * Summary: Adds item to desktop
		 * Arguments: Item settings contained id and classes
		 */
		addItem: function(type, itemSettings) {

			var classes,
				options;
				  
			if(util.is(type, 'object')) {
				itemSettings = type;
				classes = ['default'];
			} else if(util.is(type, 'string')) {
				classes = [type];
			}
			
			var itemSettings = util.extend({
				classes: classes,
				id: 'item_' + this._getId()
			}, itemSettings)

			var it = new item(itemSettings.id, itemSettings.classes, this);

			if( !this._items[it.id] ) {
			
				this._items[it.id] = it;
				this.emitEvent('addItem', it, itemSettings);
				
				return it;
			
			} else {
				return false;
			}
		},
			
		/*
		 * Summary: Remove item from desktop
		 * Arguments: Item id
		 */
		removeItem: function(itemId) {
			
			if(!this._items[itemId]) {
				return;
			}
			
			this.emitEvent('removeItem', itemId);
			delete this._items[itemId];
		},
		
		/*
		 * Summary: Shows desktop item
		 * Arguments: Item id
		 */
		showItem: function(itemId) {
			if(this._items[itemId]) {
				this.emitEvent('showItem', itemId);
			}	
		},
		
		/*
		 * Summary: Hides desktop item
		 * Arguments: Item id
		 */
		hideItem: function(itemId) {
			if(this._items[itemId]) {
				this.emitEvent('hideItem', itemId);
			}	
		},
		
		/*
		 * Summary: Removes all desktop items
		 */
		clear: function() {
			var items = this._items;
			for(var itemId in items) {
				this.removeItem(itemId);
			}
		},
		
		/*
		 * Summary: Removes desktop from dom
		 */
		destroy: function() {
			this._canvas.clear();
		},

		/**
		 * Sets desktop attributes
		 * @param  {string} attrName Name of attribute or objects contains attibutes
		 * @param  {any} value Value of attribute
		 * @return {any} If attrName is string and there is no value set, returns value of attribute
		 */
		attr: function(attrName, value) {
			if(typeof(attrName) === 'object') {
				var val;
				
				for(val in attrName) {
					if(attrName.hasOwnProperty(val) && this._settings.hasOwnProperty(val)) {
						this.emitEvent('attrChange', val, attrName[val]);
						this._settings[val] = attrName[val];
					}
				}
			} else if(this._settings.hasOwnProperty(attrName)) {
			
				if(typeof(value) !== 'undefined') {
					this.emitEvent('attrChange', attrName, value);
					this._settings[attrName] = attrName[val];
				} else {
					return this._settings[attrName];
				}
			}
		},
		
		/*
		 * Summary: sets or gets attribute of item;
		 */
		itemAttr: function(itemId, attrName, value) {

			if(this._items[itemId]) {
				if(typeof(attrName) === 'object') {
					var val;
					
					for(val in attrName) {
						if(attrName.hasOwnProperty(val) && this._items[itemId].hasOwnProperty(val)) {
							this.emitEvent('itemAttrChange', itemId, val, attrName[val]);
						}
					}
				} else if(this._items[itemId].hasOwnProperty(attrName)) {
				
					if(typeof(value) !== 'undefined') {
						this.emitEvent('itemAttrChange', itemId, attrName, value);
					} else {
						return this._items[itemId][attrName];
					}
				}
			}
		},
		
		/*
		 * Summary: log function
		 */
		log: function(level, message) {
			if(jt.DEBUG) {
				
				if(typeof(message) === 'string') {
					console.log[level](message);
				} else if(typeof(level) === 'string' && !message) {
					console.log(level);
				}
				
			} else {
				// notify
			}
		},
		
		listFunctionalities: function() {
			var member;
			
			for(member in this) {
				if(typeof(this[member]) === 'function') {
					console.log(['Method',member].join(' '));
				}
			}
 		}
	};

	// Desktop item prototype
	
	function item(id, classes, desktop) {
		this.id = id;
		this.classes = classes;
		this._desktop = desktop;
	}

	item.prototype = {
		attr: function(name, value) {
			this._desktop.itemAttr.call(this._desktop, this.id, name, value);
		}
	};

	item.prototype.constructor = item;
	
	// Expose namespace
	!global.jt && (global.jt = jt);

})(window, document);